package LevelEditor;

import ClientGraphics.GraphicsMain;
import GameEngine.GameConstants;
import GameEngine.GameConstants.*;
import GameEngine.Level;
import GameEngine.LevelInitializer;
import GameEngine.Vector;
import Objects.BasicObject;
import Objects.GameObject;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import Objects.GameObject;
import Objects.SpawnPoint;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import org.dom4j.*;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

/**
 * Class for reading and writing from/to xml files
 * @author jh20
 *
 */

public class XmlIO {

    /**
     * Outputs an xml file from the level editor
     * @param output
     * @param levelObjects
     * @param enumMap
     * @param dirMap
     * @param mainPanel
     * @throws java.io.IOException
     */
	
	public static void write(String output, List<GameObject> levelObjects, HashMap<GameObject, GameConstants.Thing> enumMap, MainPanel mainPanel) throws IOException {
		Document doc = DocumentHelper.createDocument();

        // header

        Element root = DocumentHelper.createElement("LEVEL");
        doc.setRootElement(root);

        root.addAttribute("WIDTH", String.valueOf(mainPanel.getSizeX()));
        root.addAttribute("HEIGHT", String.valueOf(mainPanel.getSizeY()));
        root.addAttribute("NUM_PLAYERS", String.valueOf(mainPanel.getNumPlayers()));

        // for each object

        for (GameObject obj : levelObjects) {


            // get its enum and direction
            Thing type = enumMap.get(obj);
            Vector dir = obj.getDir();
            if (type != null) {
                // if its enum is not null and neither is its direction, continue

                // head each object with its enum
                Element element = DocumentHelper.createElement(type.toString());
                element.addAttribute("DIR_X", String.valueOf(dir.getX()));
                element.addAttribute("DIR_Y", String.valueOf(dir.getY()));
                element.addAttribute("CEN_X", String.valueOf(obj.getCenter().getX()));
                element.addAttribute("CEN_Y", String.valueOf(obj.getCenter().getY()));
                element.addAttribute("SIZE_X", String.valueOf(obj.getSize().getX()));
                element.addAttribute("SIZE_Y", String.valueOf(obj.getSize().getY()));
                root.add(element);
            }
        }

        // write to the document
        OutputFormat format_pretty = OutputFormat.createPrettyPrint();

        XMLWriter fileWriter = new XMLWriter(new FileWriter(output), format_pretty);
        fileWriter.write(doc);
        fileWriter.flush();
	}

    /**
     * Reads things to the level editor from an xml file.
     * @param input
     * @param levelObjects
     * @param enumMap
     * @param dirMap
     * @param mainPanel
     * @throws org.dom4j.DocumentException
     * @throws java.io.FileNotFoundException
     */

    public static void read(String input, List<GameObject> levelObjects, HashMap<GameObject, GameConstants.Thing> enumMap, SpawnPoint spawnPoint, MainPanel mainPanel, GraphicsMain gMain) throws DocumentException, FileNotFoundException {
        SAXReader reader = new SAXReader();
        Document doc = reader.read(new FileReader(input));

        Element root = doc.getRootElement();

        short tempId = 0;

        // set the width and height of the level
        mainPanel.setLevelSize(Integer.parseInt(root.attributeValue("WIDTH")), Integer.parseInt(root.attributeValue("HEIGHT")));
        mainPanel.setNumPlayers(Integer.parseInt(root.attributeValue("NUM_PLAYERS")));

        float cenX, cenY, sizeX, sizeY, dirX, dirY;

        List<BasicObject> updateObjects = new LinkedList<BasicObject>();

        // iterate through child elements
        for (Iterator i = root.elementIterator(); i.hasNext();) {
            Element element = (Element) i.next();
            try {
                // try to get the data
                Thing type = Enum.valueOf(Thing.class, element.getName());

                // try to get coordinates
                try {
                    dirX = Float.parseFloat(element.attributeValue("DIR_X"));
                    dirY = Float.parseFloat(element.attributeValue("DIR_Y"));
                    cenX = Float.parseFloat(element.attributeValue("CEN_X"));
                    cenY = Float.parseFloat(element.attributeValue("CEN_Y"));
                    sizeX = Float.parseFloat(element.attributeValue("SIZE_X"));
                    sizeY = Float.parseFloat(element.attributeValue("SIZE_Y"));
                } catch (NumberFormatException e) {
                    throw new NullPointerException("Coordinate data value is null");
                }

                // if anything is null, throw an exception
                if (type == null) {
                    throw new NullPointerException("Data value is null");
                } else {
                    // otherwise, make the object

                    GameObject obj = LevelInitializer.objectConstructor(type, new Vector(dirX, dirY), new Vector(cenX, cenY), new Vector(sizeX, sizeY));
                    obj.setId(tempId);
                    tempId++;

                    // special case for spawn point
                    // only last spawn point counts
                    if (type == Thing.SPAWNPOINT) {
                        if (spawnPoint != null) {
                            levelObjects.remove(spawnPoint);
                        }
                        spawnPoint = (SpawnPoint) obj;
                    }

                    levelObjects.add(obj);
                    enumMap.put(obj, type);

                    updateObjects.add(obj);
                }
                

            } catch (NullPointerException e) {
                System.out.println("Invalid type at: " + element.getName());
                System.out.println(e.getMessage());
            } catch (IllegalArgumentException e) {
                System.out.println("Invalid type at: " + element.getName());
                System.out.println(e.getMessage());
            }
        }

        mainPanel.resetPreview();

        gMain.update(updateObjects);
        mainPanel.setTempId(tempId);

    }

    public static Level read(String input) throws DocumentException, FileNotFoundException
    {
        Level toReturn = null;
        SAXReader reader = new SAXReader();
        Document doc = reader.read(new FileReader(input));
        Element root = doc.getRootElement();
        // set the width and height of the level
        int width = Integer.parseInt(root.attributeValue("WIDTH"));
        int height = Integer.parseInt(root.attributeValue("HEIGHT"));
        int numPlayers = Integer.parseInt(root.attributeValue("NUM_PLAYERS"));

        float cenX, cenY, sizeX, sizeY, dirX, dirY;
        SpawnPoint spawnPoint = null;
        ArrayList<GameObject> levelObjects = new ArrayList<GameObject>();
        // iterate through child elements
        for (Iterator i = root.elementIterator(); i.hasNext();)
        {
            Element element = (Element) i.next();
            try
            {
                // try to get the data
                Thing type = Enum.valueOf(Thing.class, element.getName());
                System.out.println(type.valueOf(element.getName()));

                // try to get coordinates
                try
                {
                    dirX = Float.parseFloat(element.attributeValue("DIR_X"));
                    dirY = Float.parseFloat(element.attributeValue("DIR_Y"));
                    cenX = Float.parseFloat(element.attributeValue("CEN_X"));
                    cenY = Float.parseFloat(element.attributeValue("CEN_Y"));
                    sizeX = Float.parseFloat(element.attributeValue("SIZE_X"));
                    sizeY = Float.parseFloat(element.attributeValue("SIZE_Y"));
                } 
                catch (NumberFormatException e)
                {
                    throw new NullPointerException("Coordinate data value is null");
                }

                // if anything is null, throw an exception
                if (type == null)
                {
                    throw new NullPointerException("Data value is null");
                }
                else
                {
                    // otherwise, make the object

                    GameObject obj = LevelInitializer.objectConstructor(type, new Vector(dirX, dirY), new Vector(cenX, cenY), new Vector(sizeX, sizeY));

                    // special case for spawn point
                    // only last spawn point counts

                    if (type == Thing.SPAWNPOINT)
                    {
                        spawnPoint = (SpawnPoint) obj;
                    }
                    levelObjects.add(obj);
                }     
            } 
            catch (NullPointerException e)
            {
                System.out.println("Invalid type at: " + element.getName());
                System.out.println(e.getMessage());
            } 
            catch (IllegalArgumentException e)
            {
                System.out.println("Invalid type at: " + element.getName());
                System.out.println(e.getMessage());
            }
        }
        if (spawnPoint == null)
        {
            // if there isn't a spawn point, make one
            System.out.println("WARNING : NO SPAWN POINT");
            spawnPoint = new SpawnPoint(new Vector(0, 0), LevelInitializer.getDefaultSize(Thing.SPAWNPOINT, new Vector(1,0)));
            levelObjects.add(spawnPoint);
        }
        System.out.println(levelObjects.get(0).getDir().getX());
        toReturn = new Level(levelObjects, width, height, numPlayers, spawnPoint);
        System.out.println(toReturn);
        return toReturn;

    }
}
